﻿using ScottPlot;
using ScottPlot.Colormaps;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Log;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.ScottPlotEx
{
    /// <summary>
    /// 巨大瀑布图
    /// </summary>
    unsafe public class GreatWaterfallPlot : IPlottable, IHasColorAxis
    {
        #region IHasColorAxis

        private IColormap _colormap = null;
        /// <summary>
        /// 获取或设置颜色域
        /// </summary>
        public IColormap Colormap
        {
            get { return this._colormap; }
        }

        private ScottPlot.Range _dataRange;
        /// <summary>
        /// 获取数据范围
        /// </summary>
        /// <returns></returns>
        public ScottPlot.Range GetRange()
        {
            return this._dataRange;
        }

        #endregion


        private GreatWaterfallPlotOptions _options = null;
        /// <summary>
        /// 可选项参数
        /// </summary>
        public GreatWaterfallPlotOptions Options
        {
            get { return this._options; }
        }




        private readonly UnmanagedMemoryPointer<uint> _argbsPointer = new UnmanagedMemoryPointer<uint>();
        private readonly UnmanagedMemoryPointer<uint> _argbsResmplePointer = new UnmanagedMemoryPointer<uint>();


        private SKPaint _paint = null;
        private SKBitmap _bitmap = null;
        private SKImageInfo _imageInfo;
        private CoordinateRect _alignedExtent;
        private double _cellWidth;
        private double _cellHeight;

        private SKBitmap _bitmap2 = null;
        private SKCanvas _bitmap2Canvas = null;


        /// <summary>
        /// 
        /// </summary>
        public GreatWaterfallPlot()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        ~GreatWaterfallPlot()
        {
            this._bitmap?.Dispose();
            this._argbsPointer.Dispose();
            this._argbsResmplePointer.Dispose();
            this._paint?.Dispose();

            this._bitmap2?.Dispose();
            this._bitmap2Canvas?.Dispose();
        }



        /// <summary>
        /// 设置瀑布图
        /// </summary>
        /// <param name="specData">数据</param>
        /// <param name="options">可选项参数</param>
        /// <exception cref="ArgumentOutOfRangeException">参数无效异常</exception>
        /// <exception cref="ArgumentException">参数无效异常</exception>
        public void Set(SpanZ<double> specData, GreatWaterfallPlotOptions options)
        {
            this.CheckOptions(options);


            this._colormap = options.Colormap;
            if (this._colormap == null)
            {
                this._colormap = new ScottPlot.Colormaps.Viridis();
            }
            this._dataRange = new ScottPlot.Range(options.Min, options.Max);


            this._argbsPointer.CheckSetLength(options.DataWidth * options.DataHeight);
            SpanZ<uint> argbs = this._argbsPointer.GetSpanZ();
            argbs.Fill(this._colormap.GetColor(options.Min, this._dataRange).PremultipliedARGB);


            int plotWidth = options.PlotWidth;
            int plotHeight = options.PlotHeight;
            if (this.RequireCreateBitmap(this._options, options))
            {
                if (this._bitmap != null)
                {
                    this._bitmap.Dispose();
                    this._bitmap2.Dispose();
                    this._bitmap2Canvas.Dispose();
                }

                this._imageInfo = new SKImageInfo(plotWidth, plotHeight);
                this._bitmap = new SKBitmap(this._imageInfo);

                this._bitmap2 = new SKBitmap(plotWidth, plotHeight);
                this._bitmap2Canvas = new SKCanvas(this._bitmap2);
            }
            this._argbsResmplePointer.CheckSetLength(plotWidth * plotHeight);

            //翻转画布
            FlipCanvas(this._bitmap2Canvas, options, plotWidth, plotHeight);

            //创建画笔
            this._paint = CreatePaint(this._paint, options.Smooth);



            this._alignedExtent = PlotHelper.GetAlignedExtent(options.AlignedExtentOptions, new CoordinateRect(0, plotWidth, 0, plotHeight), out this._cellWidth, out this._cellHeight);
            this._axisLimits = new AxisLimits(this._alignedExtent);
            this._options = options;

            this.AppendDatas(specData, argbs, options.DataWidth, options.DataHeight, options.NaNCellColor.PremultipliedARGB);
        }

        private static SKPaint CreatePaint(SKPaint oldPaint, bool smooth)
        {
            if (oldPaint != null)
            {
                if (smooth && oldPaint.FilterQuality != SKFilterQuality.High ||
                    !smooth && oldPaint.FilterQuality != SKFilterQuality.None)
                {
                    oldPaint.Dispose();
                    oldPaint = null;
                }
            }

            if (oldPaint == null)
            {
                oldPaint = new SKPaint()
                {
                    FilterQuality = smooth ? SKFilterQuality.High : SKFilterQuality.None
                };
            }

            return oldPaint;
        }

        private static void FlipCanvas(SKCanvas bitmap2Canvas, GreatWaterfallPlotOptions options, float dx, float dy)
        {
            //重置画布
            bitmap2Canvas.ResetMatrix();

            switch (options.Direction)
            {
                case PlotMoveDirection.Down:
                    if (!options.FlipHorizontally)
                    {
                        bitmap2Canvas.Scale(-1, 1);//水平翻转
                        bitmap2Canvas.Translate(-dx, 0);
                    }

                    bitmap2Canvas.Translate(dx, dy);
                    bitmap2Canvas.RotateDegrees(180);//旋转180度
                    break;
                case PlotMoveDirection.Up:
                    if (options.FlipHorizontally)
                    {
                        bitmap2Canvas.Scale(-1, 1);//水平翻转
                        bitmap2Canvas.Translate(-dx, 0);
                    }

                    //默认向上,不做旋转操作
                    break;
                case PlotMoveDirection.Left:
                    if (options.FlipVertically)
                    {
                        bitmap2Canvas.Scale(1, -1);//垂直翻转
                        bitmap2Canvas.Translate(0, -dy);
                    }

                    bitmap2Canvas.Translate(0, dx);
                    bitmap2Canvas.RotateDegrees(270);//旋转270度
                    break;
                case PlotMoveDirection.Right:
                    if (options.FlipVertically)
                    {
                        bitmap2Canvas.Scale(1, -1);//垂直翻转
                        bitmap2Canvas.Translate(0, -dx);
                    }

                    bitmap2Canvas.Translate(dy, 0);
                    bitmap2Canvas.RotateDegrees(90);//旋转90度
                    break;
                default:
                    throw new NotImplementedException($"未实现的移动方向{options.Direction.ToString()}");
            }
        }

        private bool RequireCreateBitmap(GreatWaterfallPlotOptions oldOptions, GreatWaterfallPlotOptions newOptions)
        {
            if (this._bitmap == null || oldOptions == null)
            {
                return true;
            }

            if (this._bitmap.Width != newOptions.PlotWidth ||
                this._bitmap.Height != newOptions.PlotHeight)
            {
                return true;
            }

            if (oldOptions.Direction != newOptions.Direction ||
                oldOptions.FlipVertically != newOptions.FlipVertically ||
                oldOptions.FlipHorizontally != newOptions.FlipHorizontally)
            {
                return true;
            }

            return false;
        }

        private void CheckOptions(GreatWaterfallPlotOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (options.DataWidth < PlotConstant.SIZE_MIN)
            {
                throw new ArgumentOutOfRangeException(nameof(options.DataWidth), $"数据宽度不能小于{PlotConstant.SIZE_MIN}");
            }

            if (options.DataHeight < PlotConstant.SIZE_MIN)
            {
                throw new ArgumentOutOfRangeException(nameof(options.DataHeight), $"数据高度不能小于{PlotConstant.SIZE_MIN}");
            }

            if (!DoubleEx.Valid(options.Min))
            {
                throw new ArgumentException("最小值无效", nameof(options.Min));
            }

            if (!DoubleEx.Valid(options.Max))
            {
                throw new ArgumentException("最大值无效", nameof(options.Max));
            }

            if (options.Min > options.Max)
            {
                throw new ArgumentException("最小值不能大于最大值");
            }

            if (options.Direction != PlotMoveDirection.Up &&
                options.Direction != PlotMoveDirection.Down &&
                options.Direction != PlotMoveDirection.Left &&
                options.Direction != PlotMoveDirection.Right)
            {
                throw new NotSupportedException($"不支持的移动方向{options.Direction.ToString()}");
            }
        }

        /// <summary>
        /// 追加连续的多帧数据
        /// </summary>
        /// <param name="specData">多行频谱数据</param>
        /// <param name="argbs"></param>
        /// <param name="dataWidth"></param>
        /// <param name="dataHeight"></param>
        /// <param name="nanCellArgb">NAN数据颜色ARGB值</param>
        private void AppendDatas(SpanZ<double> specData, SpanZ<uint> argbs, long dataWidth, long dataHeight, uint nanCellArgb)
        {
            long framCount = specData.Length / dataWidth;
            if (framCount > dataHeight)
            {
                specData = specData.Slice((framCount - dataHeight) * dataWidth, dataWidth * dataHeight);
                framCount = dataHeight;
            }

            double* dataPtr = specData.DataPtr;
            uint* argbsPtr = argbs.DataPtr;
            Parallel.For(0, framCount, (rowIndex) =>
            {
                long step = rowIndex * dataWidth;
                double* perFramSpecDataPtr = dataPtr + step;
                uint* perFramArgbsPtr = argbsPtr + step;
                double value;

                for (long j = 0; j < dataWidth; j++)
                {
                    value = perFramSpecDataPtr[j];

                    if (double.IsNaN(value))
                    {
                        perFramArgbsPtr[j] = nanCellArgb;
                    }
                    else
                    {
                        perFramArgbsPtr[j] = this._colormap.GetColor(value, this._dataRange).PremultipliedARGB;
                    }
                }
            });
        }




        #region IPlottable

        private bool _isVisible = true;
        /// <summary>
        /// 是否可见
        /// </summary>
        public bool IsVisible
        {
            get { return this._isVisible; }
            set { this._isVisible = value; }
        }


        private IAxes _axes = new Axes();
        /// <summary>
        /// 获取或设置坐标系
        /// </summary>
        public IAxes Axes
        {
            get { return this._axes; }
            set { this._axes = value; }
        }

        private AxisLimits _axisLimits;
        /// <summary>
        /// 获取坐标范围
        /// </summary>
        /// <returns></returns>
        public AxisLimits GetAxisLimits()
        {
            return this._axisLimits;
        }

        /// <summary>
        /// 渲染图
        /// </summary>
        /// <param name="rp"></param>
        public void Render(RenderPack rp)
        {
            if (this._bitmap == null)
            {
                return;
            }

            //var dataRect= rp.DataRect;
            //var dataRect = rp.ScaledFigureRect;
            //var dataRect = rp.FigureRect;
            //var dataRect = rp.Canvas.DeviceClipBounds;
            //Loger.Info($"x: {dataRect.Left} - {dataRect.Right}     y: {dataRect.Top} - {dataRect.Bottom}");

            PlotScaleInfo scaleInfo = PlotHelper.GetScale(this._axes, this._imageInfo.Width, this._imageInfo.Height);
            SpanZ<uint> srcArgbs = this._argbsPointer.GetSpanZ();
            SpanZ<uint> resampleArgbs = this._argbsResmplePointer.GetSpanZ();
            ResampleArgb(this._axes, scaleInfo, srcArgbs, resampleArgbs);
            this._bitmap.InstallPixels(this._imageInfo, new IntPtr(resampleArgbs.DataPtr), this._imageInfo.RowBytes, null, null);
            this._bitmap2Canvas.DrawBitmap(this._bitmap, 0, 0, this._paint);
            SKRect destRect = this._axes.GetPixelRect(this._alignedExtent).ToSKRect();
            //Loger.Info($"x: {destRect.Left} - {destRect.Right}     y: {destRect.Top} - {destRect.Bottom}");
            this._paint.IsStroke = true;
            rp.Canvas.DrawRect(destRect, this._paint);
            this._paint.IsStroke = false;
            rp.Canvas.DrawBitmap(this._bitmap2, destRect, this._paint);
        }

        private double? _scale = null;
        private void ResampleArgb(IAxes axes, PlotScaleInfo scaleInfo, SpanZ<uint> srcArgbs, SpanZ<uint> destArgbs)
        {
            var scale = scaleInfo.GetMax();
            Loger.Info($"ScaleX: {scaleInfo.ScaleX} ScaleY: {scaleInfo.ScaleY}          scale :{scale}");
            
            if (this._scale.HasValue && Math.Abs(scale - this._scale.Value) < 2)
            {
                return;
            }
            this._scale = scale;

            var extratCols = this._options.DataWidth / this._options.PlotWidth;
            var extratRows = this._options.DataHeight / this._options.PlotHeight;
            var extratColsMod = this._options.DataWidth % this._options.PlotWidth;
            var extratRowsMod = this._options.DataHeight % this._options.PlotHeight;
            uint* srcArgbsPtr = srcArgbs.DataPtr;
            uint* destArgbsPtr = destArgbs.DataPtr;
            int lastDestRowIndex = this._options.PlotHeight - 1;
            int lastDestColIndex = this._options.PlotWidth - 1;


            Parallel.For(0, this._options.PlotHeight, (destRowIndex) =>
            {
                uint* srcArgbsPtr2 = srcArgbsPtr + destRowIndex * extratRows * this._options.PlotWidth;
                uint* srcArgbsPtr3;
                uint* destArgbsPtr2 = destArgbsPtr + destRowIndex * this._options.PlotWidth;
                uint value;
                long offset;

                long extratRows2 = extratRows;
                if (destRowIndex == lastDestRowIndex && extratRowsMod != 0)
                {
                    extratRows2 = extratRowsMod;
                }

                long extratCols2 = extratCols;

                for (int destIndex = 0; destIndex < this._options.PlotWidth; destIndex++)
                {
                    offset = destIndex * extratCols;
                    srcArgbsPtr3 = srcArgbsPtr2 + offset;
                    value = *srcArgbsPtr3;

                    if (destIndex == lastDestColIndex && extratColsMod != 0)
                    {
                        extratCols2 = extratColsMod;
                    }

                    for (int row = 0; row < extratRows2; row++)
                    {
                        for (int col = 0; col < extratCols2; col++)
                        {
                            value = Math.Max(value, srcArgbsPtr3[col]);
                        }
                        srcArgbsPtr3 += this._options.DataWidth;
                    }

                    destArgbsPtr2[destIndex] = value;
                }
            });



            //
            //CoordinateRangeMutable xrange = axes.XAxis.Range;
            //CoordinateRangeMutable yrange = axes.YAxis.Range;
            //////Loger.Info($"XAxisRange:{Math.Round(_axes.XAxis.Min, 2)}-{Math.Round(_axes.XAxis.Max, 2)}, XAxisShowRange:{Math.Round(xrange.Min, 2)}-{Math.Round(xrange.Max, 2)},Span:{Math.Round(xrange.Span, 2)}     YAxisRange:{Math.Round(_axes.YAxis.Min, 2)}-{Math.Round(_axes.YAxis.Max, 2)},YAxisShowRange:{Math.Round(yrange.Min, 2)}-{Math.Round(yrange.Max, 2)}, Span:{Math.Round(yrange.Span, 2)}");
            //Loger.Info($"XAxisShowRange:{Math.Round(xrange.Min, 2)}-{Math.Round(xrange.Max, 2)},Span:{Math.Round(xrange.Span, 2)}     YAxisShowRange:{Math.Round(yrange.Min, 2)}-{Math.Round(yrange.Max, 2)}, Span:{Math.Round(yrange.Span, 2)}");
            //Loger.Info($"XAxisShowRange:{Math.Round(xrange.Min, 2)}-{Math.Round(xrange.Max, 2)},Span:{Math.Round(xrange.Span, 2)}");

        }

        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<LegendItem> LegendItems
        {
            get { return Enumerable.Empty<LegendItem>(); }
        }

        #endregion

    }

    /// <summary>
    /// 可选项参数
    /// </summary>
    public class GreatWaterfallPlotOptions
    {
        private static GreatWaterfallPlotOptions _default = null;
        /// <summary>
        /// 默认可选项参数
        /// </summary>
        public static GreatWaterfallPlotOptions Default
        {
            get
            {
                if (_default == null)
                {
                    _default = new GreatWaterfallPlotOptions();
                }

                return _default;
            }
        }


        /// <summary>
        /// 构造函数初始化
        /// </summary>
        public GreatWaterfallPlotOptions()
        {

        }




        /// <summary>
        /// 数据宽带
        /// </summary>
        public long DataWidth { get; set; }

        /// <summary>
        /// 数据高带
        /// </summary>
        public long DataHeight { get; set; }

        /// <summary>
        /// 图宽度
        /// </summary>
        public int PlotWidth { get; set; }

        /// <summary>
        /// 图高度
        /// </summary>
        public int PlotHeight { get; set; }

        /// <summary>
        /// 频谱图中的最小值
        /// </summary>
        public double Min { get; set; }

        /// <summary>
        /// 频谱图中的最大值
        /// </summary>
        public double Max { get; set; }

        /// <summary>
        /// 色域
        /// </summary>
        public IColormap Colormap { get; set; } = new Turbo();

        /// <summary>
        /// 瀑布图移动方向
        /// </summary>
        public PlotMoveDirection Direction { get; set; } = PlotMoveDirection.Up;



        /// <summary>
        /// 对齐选项参数
        /// </summary>
        public PlotAlignedExtentOptions AlignedExtentOptions { get; set; } = null;

        /// <summary>
        /// 垂直翻转[true:翻转;false:不翻转],水平方向移动时有效
        /// </summary>
        public bool FlipVertically { get; set; } = false;

        /// <summary>
        /// 水平翻转[true:翻转;false:不翻转],垂直方向移动时有效
        /// </summary>
        public bool FlipHorizontally { get; set; } = false;

        /// <summary>
        /// If true, pixels in the final image will be interpolated to give the heatmap a smooth appearance.
        /// If false, the heatmap will appear as individual rectangles with sharp edges.
        /// </summary>
        public bool Smooth { get; set; } = false;

        /// <summary>
        /// Nan值点的颜色
        /// </summary>
        public ScottPlot.Color NaNCellColor { get; set; } = Colors.Transparent;

        /// <summary>
        /// 是否启用原始数据缓存，缓存后才可通过获取指定区域的值
        /// </summary>
        public bool CacheOriginValue { get; set; } = false;

    }
}
